package com.atdel.demo24;

import com.atdel.demo06.User;
import com.atdel.demo23.Student;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.*;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

import static java.sql.DriverManager.getConnection;

/**
 * @BelongsProject: java_study
 * @BelongsPackage: com.atdel.demo24
 * @Author: Del
 * @CreateTime: 2025-03-19  09:41
 * @Description:
 *  1、根据参数 beanClass 自动生成查询所有数据的 SQL 语句，
 *     2、cond 为查询条件，需要拼接到查询所有数据的后面，
 *     3、把查询的数据封装到对应的实体对象返回
 * @Version: 1.0
 */
public class DbUtil {



    /**
     * @author: Del
     * @date: 2025/3/19 10:08
     * @param: [beanClass：Bean类的Class对象，用于获取表名和字段名, cond：查询条件的Map，键为字段名，值为字段值]
     * @throws IllegalArgumentException 如果beanClass为null，则抛出此异常
     * @throws RuntimeException 如果在生成SQL查询语句过程中出现异常，则抛出此异常
     * @return: java.lang.String 生成的SQL查询语句
     * @description: 生成基于给定条件的SQL SELECT语句
     * 该方法根据Bean类和条件参数动态生成SQL查询语句
     **/
    public static <T> String select4Bean(Class<T> beanClass, Map<String, Object> cond) {
        // 检查beanClass是否为null，如果为null，则抛出异常
        if (beanClass == null) {
            throw new IllegalArgumentException("beanClass cannot be null");
        }

        try {
            // 反射HashMap
            Class<?> hashMapClass = Class.forName("java.util.HashMap");
            Object hashMapInstance = hashMapClass.getDeclaredConstructor().newInstance();
            Method putMethod = hashMapClass.getMethod("put", Object.class, Object.class);

            // 反射StringBuilder
            Class<?> stringBuilderClass = Class.forName("java.lang.StringBuilder");
            Object stringBuilderInstance = stringBuilderClass.getDeclaredConstructor().newInstance();
            Method appendMethod = stringBuilderClass.getMethod("append", CharSequence.class);
            Method toStringMethod = stringBuilderClass.getMethod("toString");

            // 获取类的属性数组
            Field[] declaredFields = beanClass.getDeclaredFields();

            // 构建 SELECT 部分
            StringBuilder selectBuilder = (StringBuilder) stringBuilderInstance;
            appendMethod.invoke(selectBuilder, "SELECT ");
            for (int i = 0; i < declaredFields.length; i++) {
                String fieldName = getTransitionTable(declaredFields[i].getName());
                appendMethod.invoke(selectBuilder, fieldName);
                if (i < declaredFields.length - 1) {
                    appendMethod.invoke(selectBuilder, ", ");
                }
            }
            appendMethod.invoke(selectBuilder, " FROM ");
            appendMethod.invoke(selectBuilder, getTransitionTable(beanClass.getSimpleName()).toLowerCase());

            // 构建 WHERE 部分
            if (cond != null && !cond.isEmpty()) {
                appendMethod.invoke(selectBuilder, " WHERE ");
                int i = 0;
                for (Map.Entry<String, Object> entry : cond.entrySet()) {
                    appendMethod.invoke(selectBuilder, entry.getKey());
                    appendMethod.invoke(selectBuilder, " = ?");
                    if (i < cond.size() - 1) {
                        appendMethod.invoke(selectBuilder, " AND ");
                    }
                    i++;
                }
            }
            return (String) toStringMethod.invoke(selectBuilder);
        } catch (Exception e) {
            throw new RuntimeException("Error generating SQL query", e);
        }
    }

    /**
     * 查询数据并封装到对应的实体对象中。
     *
     * @param beanClass 实体类的 Class 对象。
     * @param cond      查询条件的 Map，键为字段名，值为字段值。
     * @param <T>       实体类的类型。
     * @return 返回查询到的实体对象，如果未查询到数据则返回 null。
     * @throws RuntimeException 如果查询过程中发生异常。
     */
    public static <T> T selectBean(Class<T> beanClass, Map<String, Object> cond) {
        // 生成 SQL 查询语句
        String sql = select4Bean(beanClass, cond);

        try (Connection connection = getConnection("jdbc:mysql://localhost:3306/db_test", "root", "459633");
             PreparedStatement preparedStatement = connection.prepareStatement(sql)) {

            // 设置查询参数
            if (cond != null && !cond.isEmpty()) {
                int i = 1;
                for (Map.Entry<String, Object> entry : cond.entrySet()) {
                    preparedStatement.setObject(i++, entry.getValue());
                }
            }

            // 执行查询并获取结果集
            ResultSet resultSet = preparedStatement.executeQuery();

            // 如果结果集为空，返回 null
            if (!resultSet.next()) {
                return null;
            }

            // 创建实体对象
            T entity = beanClass.getDeclaredConstructor().newInstance();

            // 将结果集的数据映射到实体对象中
            mapResultSetToEntity(resultSet, entity);

            return entity;
        } catch (Exception e) {
            throw new RuntimeException("Error executing SQL query", e);
        }
    }


    /**
     * 将结果集的数据映射到实体对象中。
     *
     * @param resultSet 查询结果集。
     * @param entity    实体对象。
     * @param <T>       实体类的类型。
     * @throws Exception 如果映射过程中发生异常。
     */
    private static <T> void mapResultSetToEntity(ResultSet resultSet, T entity) throws Exception {
        Class<?> clazz = entity.getClass();
        for (Field field : clazz.getDeclaredFields()) {
            if (field.isAnnotationPresent(Column.class)) {
                field.setAccessible(true); // 设置字段可访问（即使是私有字段）

                // 从 @Column 注解中获取数据库列名
                Column columnAnnotation = field.getAnnotation(Column.class);
                String columnName = columnAnnotation.columnName(); // 使用注解中的列名
                if (columnName == null || columnName.isEmpty()) {
                    columnName = field.getName(); // 如果注解未指定列名，则默认使用字段名
                }

                Object value = resultSet.getObject(columnName); // 从结果集中获取字段值
                if (value != null) {
                    field.set(entity, convertValue(field.getType(), value));
                } else {
                    field.set(entity, getDefaultValueForType(field.getType()));
                }
            }
        }
    }

  /**
   * 将给定值转换为目标字段类型
   * 此方法旨在处理不同数据类型之间的安全转换，特别是在处理动态类型数据时非常有用
   * 它根据目标字段的类型将输入值转换为适当的形式，以确保数据一致性和类型安全
   *
   * @param fieldType 目标字段的类型，表示期望的返回值类型
   * @param value 要转换的原始值，可以是任何类型
   * @return 根据目标字段类型转换后的值
   * @author Del
   * @date 2025/3/20 19:06
   */
  private static Object convertValue(Class<?> fieldType, Object value) {
      // 如果值为 null，则返回目标类型的默认值
      if (value == null) {
          return getDefaultValueForType(fieldType);
      }
      // 处理从 LocalDateTime 到 Date 的转换
      if (fieldType == java.util.Date.class && value instanceof java.time.LocalDateTime) {
          // 将 LocalDateTime 转换为 Date
          return java.util.Date.from(((java.time.LocalDateTime) value).atZone(java.time.ZoneId.systemDefault()).toInstant());
      } else if (fieldType == int.class && value instanceof Number) {
          // 确保转换为 int
          return ((Number) value).intValue();
      } else if (fieldType == long.class && value instanceof Number) {
          // 确保转换为 long
          return ((Number) value).longValue();
      } else if (fieldType == double.class && value instanceof Number) {
          // 确保转换为 double
          return ((Number) value).doubleValue();
      } else if (fieldType == boolean.class && value instanceof String) {
          // 确保转换为 boolean
          return Boolean.parseBoolean((String) value);
      } else {
          // 如果不需要特殊处理，则直接返回原始值
          return value;
      }
  }


  /**
   * 获取指定类型字段的默认值
   * 此方法旨在为基本类型及其包装类提供默认值，以用于初始化或重置字段
   * 对于非基本类型和其包装类，方法返回null，表示不支持这些类型
   *
   * @param fieldType 字段的类型，可以是基本类型或其包装类
   * @return 字段的默认值如果类型不支持，则返回null
   * @author Del
   * @date 2025/3/20 19:06
   **/
  private static Object getDefaultValueForType(Class<?> fieldType) {
      // 为int类型（包括int的基本类型和Integer的包装类）返回默认值0
      if (fieldType == int.class || fieldType == Integer.class) {
          return 0;
      // 为long类型（包括long的基本类型和Long的包装类）返回默认值0L
      } else if (fieldType == long.class || fieldType == Long.class) {
          return 0L;
      // 为double类型（包括double的基本类型和Double的包装类）返回默认值0.0
      } else if (fieldType == double.class || fieldType == Double.class) {
          return 0.0;
      // 为boolean类型（包括boolean的基本类型和Boolean的包装类）返回默认值false
      } else if (fieldType == boolean.class || fieldType == Boolean.class) {
          return false;
      // 对于不支持的类型，返回null
      } else {
          return null;
      }
  }




    /**
     * @author: Del
     * @date: 2025/3/19 9:56
     * @param: [tableName: 表名，预期为符合一定规则的字符串]
     * @return: java.lang.String
     * @description: 将给定的表名转换为符合特定规则的字符串。如果表名为空，抛出IllegalArgumentException异常。
     *               如果表名不符合规则，对其进行转换，转换规则包括：
     *               1. 将大写字母转换为小写字母，并在大写字母前添加下划线（除非前一个字符是下划线）。
     *               2. 移除连续的下划线。
     *               3. 保持原有的下划线和小写字母不变。
     **/
    private static String getTransitionTable(String tableName) {
        // 使用正则表达式处理表名
        String regex = "^[a-z0-9]+(_[a-z0-9]+)*$"; // 支持数字和下划线分隔的小写字母
        // 输入校验
        if (tableName.isEmpty()) {
            throw new IllegalArgumentException("表名不能为空");
        }
        // 判断是否符合正则表达式
        if (!tableName.matches(regex)) {
            StringBuilder result = new StringBuilder();
            boolean previousIsUnderscore = false;
            for (int i = 0; i < tableName.length(); i++) {
                char c = tableName.charAt(i);
                if (Character.isUpperCase(c)) {
                    // 如果当前字符是大写字母且前一个字符不是下划线，则添加下划线
                    if (i > 0 && !previousIsUnderscore) {
                        result.append("_");
                    }
                    result.append(Character.toLowerCase(c));
                    previousIsUnderscore = false;
                } else if (c == '_') {
                    // 处理连续下划线的情况
                    if (!previousIsUnderscore) {
                        result.append(c);
                        previousIsUnderscore = true;
                    }
                } else {
                    // 其他字符直接追加
                    result.append(Character.toLowerCase(c));
                    previousIsUnderscore = false;
                }
            }
            tableName = result.toString();
        }
        return tableName;
    }


    /***
     * @author: Del
     * @date: 2025/3/19 11:31
     * @param: [entity: 实体对象，包含需要插入的数据]
     * @return: int： 插入操作影响的行数。
     * @throws IllegalArgumentException 如果提供的类不是有效的实体类
     * @description: 插入一个实体类的记录到数据库中
     **/
    public static int insert(Object entity) {
        Class<?> beanClass = entity.getClass();
        if (!isEntityClass(beanClass)) {
            throw new IllegalArgumentException("提供的类不是一个有效的实体类");
        }

        // 获取实体类的字段及其值
        Map<String, Object> fieldValues = extractFieldValues(entity);

        // 生成SQL语句
        String tableName = getTableNameFromAnnotation(beanClass);
        String sql = generateInsertSql(tableName, fieldValues.keySet());

        // 执行SQL插入操作
        return executeInsert(sql, fieldValues);
    }




    /**
     * @author: Del
     * @date: 2025/3/19 11:32
     * @param: [entity： 实体对象]
     * @return: java.util.Map<java.lang.String, java.lang.Object>  包含字段名和对应字段值的 Map。
     * @description: 提取实体对象中带有 @Column 注解的字段及其值。
     **/
    private static Map<String, Object> extractFieldValues(Object entity) {
        Map<String, Object> fieldValues = new HashMap<>();
        try {
            Class<?> clazz = entity.getClass();
            for (Field field : clazz.getDeclaredFields()) {
                if (field.isAnnotationPresent(Column.class)) {
                    field.setAccessible(true); // 设置字段可访问（即使是私有字段）
                    fieldValues.put(field.getName(), field.get(entity)); // 获取字段值
                }
            }
        } catch (Exception e) {
            throw new RuntimeException("Error extracting field values", e);
        }
        return fieldValues;
    }


    /**
     * @author: Del
     * @date: 2025/3/19 11:32
     * @param: [clazz：clazz 要检查的类]
     * @return: boolean  如果给定的类是一个有效的实体类，则返回 true；否则返回 false。
     * @description: 检查给定的类是否是一个有效的实体类。
     **/
    private static boolean isEntityClass(Class<?> clazz) {
        return clazz.isAnnotationPresent(Entity.class);
    }

    /***
     * @author: Del
     * @date: 2025/3/19 11:33
     * @param: [clazz：要获取注解表名的 Class 对象]
     * @return: java.lang.String 返回表名
     * @throws IllegalArgumentException 如果表名未找到。
     * @description: 从类的注解中提取表名。
     **/
    private static String getTableNameFromAnnotation(Class<?> clazz) {
        Entity entityAnnotation = clazz.getAnnotation(Entity.class);
        if (entityAnnotation != null && !entityAnnotation.table().isEmpty()) {
            return entityAnnotation.table();
        } else {
            // 直接从但类名 转换为表名
            return getTransitionTable(clazz.getSimpleName());
        }
    }


    /**
     * @author: Del
     * @date: 2025/3/19 11:33
     * @param: [tableName：目标表的名称，columnNames：需要插入的列名集合]
     * @return: java.lang.String 生成的插入 SQL 语句。
     * @description: 生成插入数据的 SQL 语句。
     **/
    private static String generateInsertSql(String tableName, Iterable<String> columnNames) {
        StringBuilder columns = new StringBuilder();
        StringBuilder placeholders = new StringBuilder();
        for (String columnName : columnNames) {
            columnName = getTransitionTable(columnName);
            if (columns.length() > 0) {
                columns.append(", ");
                placeholders.append(", ");
            }
            columns.append(columnName);
            placeholders.append("?");
        }
        return "INSERT INTO " + tableName + " (" + columns + ") VALUES (" + placeholders + ")";
    }


    /**
     * @author: Del
     * @date: 2025/3/19 11:34
     * @param: [sql：需要执行的插入 SQL 语句, fieldValues：字段名和字段值的映射，用于设置 SQL 中的占位符。]
     * @return: int   返回受影响的行数。
     * @description: 执行插入数据的 SQL 语句。
     **/
    private static int executeInsert(String sql, Map<String, Object> fieldValues) {
        Connection connection = null;
        PreparedStatement preparedStatement = null;

        String url = "jdbc:mysql://localhost:3306/db_test";
        String user = "root";
        String password = "459633";

        try {
            // 获取数据库连接
            connection = DriverManager.getConnection(url, user, password);
            // 预编译 SQL 语句
            preparedStatement = connection.prepareStatement(sql);

            // 遍历字段值映射，设置 SQL 中的占位符
            int index = 1;
            for (Map.Entry<String, Object> entry : fieldValues.entrySet()) {
                preparedStatement.setObject(index++, entry.getValue());
            }

            // 执行 SQL 并返回受影响的行数
            return preparedStatement.executeUpdate();
        } catch (SQLException e) {
            throw new RuntimeException("Error executing SQL insert", e);
        } finally {
            // 关闭资源
            closeResources(connection, preparedStatement);
        }
    }


    /**
     * @author: Del
     * @date: 2025/3/19 11:34
     * @param: [connection：数据库连接对象, preparedStatement：预编译语句对象]
     * @return: void
     * @description:
     * 关闭数据库连接和预编译语句对象，释放资源。
     **/
    private static void closeResources(Connection connection, PreparedStatement preparedStatement) {
        try {
            if (preparedStatement != null) preparedStatement.close();
            if (connection != null) connection.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }


    public static void main(String[] args) {
        Map<String, Object> cond = new HashMap<>();
        cond.put("id", 3);
        UserInfo userInfo = selectBean(UserInfo.class, cond);
        String s = select4Bean(UserInfo.class, cond);
        System.out.println(s);
        System.out.println(userInfo);

//        System.out.println(userInfo);
//        // 创建一个 User 对象并填充数据
//        UserInfo user = new UserInfo(1002, "李四", 18);
//        int result = DbUtil.insert(user);
//        System.out.println("插入结果：" + result);
    }
}
